﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XcoAppSpaces.Core;

namespace SharedTextEditor
{
    public class DocumentServer
    {
        private XcoAppSpace space;

        public event Action<Document> DocumentChanged;


        public DocumentServer()
        {

        }

        private Document document;

        public Document Document
        {
            get
            {
                if (document == null)
                {
                    document = new Document("Test", "Test");
                }

                return document;
            }
            set
            {
                if (value != null)
                {
                    document = value;

                    if (DocumentChanged != null)
                    {
                        DocumentChanged(Document);
                    }
                }
            }
        }

        public Guid Id { get; set; }

        public string Address
        {
            get
            {
                if (space != null)
                    return space.Address;
                return "";
            }
        }

        public void Start()
        {
            space = new XcoAppSpace("tcp.port=0");// 0 => finds next free port
            space.Run<DocumentMessage>(ProcessDocumentMessage);
            space.Run<LockLinesMessage>(ProcessLockLinesMessage);
            space.Run<UpdateLinesMessage>(ProcessUpdateLinesMessage);
            space.Run<PingMessage>(ProcessPingMessage);
        }

        // Send ping back to client
        public void ProcessPingMessage(PingMessage msg)
        {
            //Console.WriteLine("[Server] Ping");
        }

        // Include updated lines in own document
        public void ProcessUpdateLinesMessage(UpdateLinesMessage msg)
        {
            Console.WriteLine("[Server] Update lines");


            Document.Lines.Sort();


            foreach (var pair in msg.UpdatedLines)
            {
                if (pair.Value.Count() == 1 && Document.Lines.Count <= (pair.Key))
                {
                    // add it at the far end
                    Document.Lines.Add( new DocumentLine(pair.Key, pair.Value, false));
                }
                else if (pair.Value == String.Empty)
                {

                    // all lines AFTER this index -1
                    foreach (var line in Document.Lines)
                    {
                        if (line.LineNumber > pair.Key)
                        {
                            line.LineNumber--;
                        }
                    }

                    // remove line
                    Document.Lines.RemoveAt(pair.Key);

                }
                else
                {
                    if (pair.Value.Count() != 1)
                    {
                        // just update the current line
                        Document.Lines.ElementAt(pair.Key).Text = pair.Value;
                    }
                    else
                    {
                        // keys of the rows that are AFTER this line +1
                        foreach (var line in Document.Lines)
                        {
                            if (line.LineNumber >= pair.Key)
                            {
                                line.LineNumber++;
                            }
                        }

                        // line added between two existings lines
                        Document.Lines.Insert(pair.Key, new DocumentLine(pair.Key, pair.Value, false));
                    }

                    
                }


                
            }

            if (DocumentChanged != null)
            {
                DocumentChanged(Document);
            }
        }

        // Lock or unlock lines
        public void ProcessLockLinesMessage(LockLinesMessage msg)
        {
            Console.WriteLine("[Server] Lock lines");
            foreach (var documentLine in Document.Lines)
            {
                if (msg.LineNumbers.Contains(documentLine.LineNumber))
                {
                    documentLine.Locked = msg.Lock;
                }
            }

            if (DocumentChanged != null)
            {
                DocumentChanged(Document);
            }
        }

        // Send document back to client
        public void ProcessDocumentMessage(DocumentMessage msg)
        {
            Console.WriteLine("[Server] Send document");
            msg.Document = Document;
            msg.Id = Id;
            msg.ResponsePort.Post(msg);
        }

        public void Dispose()
        {
          if (space != null)
            space.Dispose();
        }
    }
}
